<cfcomponent name="Collection" hint="Object intended to contain and act upon many objects of the same class.&nbsp; This object is responsible for retrieving sets of like entities based on specified criteria, containing the result as a record set or array of entity object, and acting upon that set if required.&nbsp; This is the root Collection entity and is not intended to be used directly, but rather extended by sub-class entities that collect specific entity types and posess specialized methods for building and manipulating collections.&nbsp; Collections are intended to be intelligent containers that carry multiple objects or records representing objects.&nbsp; By storing information in the properties such as what criteria was used to load it, when it was loaded, etc. collections can be stored in persistent scopes and over time communicate to the application about it's origins." >


	<!--- Properties --->


	<cfset variables.instance = StructNew() />


	<!--- Accessors --->


	<cffunction name="setId" access="public" returntype="void" output="false">
		<cfthrow type="API.Error" message="The id property of a collection is read only." detail="An attempt was made by the application to set the id property of a collection, which is prohibited." />
	</cffunction>
	<cffunction name="getId" access="public" returntype="string" output="false">
		<cfreturn variables.instance.id />
	</cffunction>

	<cffunction name="setRecordSet" access="public" returntype="void" output="false">
		<cfargument name="qVal" type="query" required="true" />
		<cfset var local = StructNew() />
		<!--- Before anything else, perform good housekeeping and call setDefaults to zero out the collection --->
		<cfset setDefaults() />
		<!--- Privately store the provided record set --->
		<cfset variables.instance.recordSet = arguments.qVal />
		<!--- Size the array to match the number of records now in the recordSet (CF recommends for performance) --->
		<cfif (arguments.qVal.RecordCount GT 0)>
			<cfset ArrayResize(variables.instance.objectSet, arguments.qVal.RecordCount) />
		</cfif>
	</cffunction>
	<cffunction name="getRecordSet" access="public" returntype="query" output="false">
		<cfthrow type="API.Error" message="The recordSet property is off-limits and purposely inaccessible to the application." detail="This prevents coupling between the application and the database schema.  Use the object set from this collection to interact with data via the object getters and setters." />
	</cffunction>

	<cffunction name="setObjectSet" access="public" returntype="void" output="false">
		<cfargument name="objectSet" type="array" required="true" />
		<cfset variables.instance.objectSet = arguments.objectSet />
	</cffunction>
	<cffunction name="getObjectSet" access="public" returntype="array" output="false">
		<cfargument name="blnFullLoad" type="boolean" required="false" default="false" hint="Used by application code that wants immediate access to the entire set as an array." />
		<cfset var local = StructNew() />
		<!--- Can't use the getCount() method here because it calls into this method and becomes recursive --->
		<cfset local.nCount = ArrayLen(variables.instance.objectSet) />
		<!---
			If the intent here is to receive a fully populated object array, there's no way to
			accomplish this and at the same time make use of any lazy loading.  Load all items
			in the set if the optional blnFullLoad is true.
		--->
		<cfif (local.nCount GT 0) AND (arguments.blnFullLoad EQ true)>
			<cfloop from="1" to="#local.nCount#" index="i">
				<cfset local.oTemp = getAt(i) />
			</cfloop>
		</cfif>
		<cfreturn variables.instance.objectSet />
	</cffunction>

	<cffunction name="setCargoTypePath" access="public" returntype="void" output="false">
		<cfargument name="cargoTypePath" type="string" required="true" />
		<cfset variables.instance.cargoTypePath = arguments.cargoTypePath />
	</cffunction>
	<cffunction name="getCargoTypePath" access="public" returntype="string" output="false">
		<cfreturn variables.instance.cargoTypePath />
	</cffunction>

	<cffunction name="setLastLoadedDate" access="public" returntype="void" output="false">
		<cfargument name="lastLoadedDate" type="string" required="true" />
		<cfif isDate(arguments.lastLoadedDate)>
			<!--- Ensures proper formatting --->
			<cfset arguments.lastLoadedDate = DateFormat(arguments.lastLoadedDate,"MM/DD/YYYY") & "" & TimeFormat(arguments.lastLoadedDate, "HH:mm:ss") />
		</cfif>
		<cfset variables.instance.lastLoadedDate = trim(arguments.lastLoadedDate) />
	</cffunction>
	<cffunction name="getLastLoadedDate" access="public" returntype="string" output="false">
		<cfreturn variables.instance.lastLoadedDate />
	</cffunction>

	<cffunction name="setLastLoadedMethod" access="public" returntype="void" output="false">
		<cfargument name="lastLoadedMethod" type="string" required="true" />
		<cfset variables.instance.lastLoadedMethod = arguments.lastLoadedMethod />
	</cffunction>
	<cffunction name="getLastLoadedMethod" access="public" returntype="string" output="false">
		<cfreturn variables.instance.lastLoadedMethod />
	</cffunction>

	<cffunction name="setLastLoadedKeySet" access="public" returntype="void" output="false">
		<cfargument name="lastLoadedKeySet" type="struct" required="true" />
		<cfset variables.instance.lastLoadedKeySet = arguments.lastLoadedKeySet />
	</cffunction>
	<cffunction name="getLastLoadedKeySet" access="public" returntype="struct" output="false">
		<cfreturn variables.instance.lastLoadedKeySet />
	</cffunction>

	<cffunction name="getDAOPath" access="public" returntype="string">
		<cfreturn variables.instance.DAOPath />
	</cffunction>
	<cffunction name="setDAOPath" access="public" returntype="void">
		<cfargument name="sVal" type="string" required="true" hint="This must be a valid component path in the ColdFusion API." />
		<cfset variables.instance.DAOPath = arguments.sVal />
	</cffunction>


	<!--- Public methods --->


	<cffunction name="init" returnType="com.revenea.root.Collection" access="public" output="false" hint="Initializes the object, thereby returning an instantiation of it populated with it&iuml;&iquest;&frac12;s default properties.&nbsp; While subclasses will inherit this method, they will use their own setDefaults method to populate their subclass properties.">
		<cfset setDefaults() />
		<cfreturn this />
	</cffunction>

	<cffunction name="getDAO" access="public" returntype="Any" hint="Using the DAOPath attribute/property, this method will retrieve an instantiation of the DAO for this data class to use.">
		<!--- Check to see if the DAO is already instantiated and caches in the private local scope --->
		<cfif (NOT IsDefined("Variables.oDAO"))>
			<!--- Not found, so init and store locally --->
			<cfset variables.instance.oDAO = CreateObject("component", getDAOPath()).init() />
		</cfif>
		<!--- Return the locally cached DAO object --->
		<cfreturn variables.instance.oDAO />
	</cffunction>

	<cffunction name="getCount" access="public" returntype="numeric" hint="Returns the number of items held in the collection.">
		<cfreturn ArrayLen(getObjectSet()) />
	</cffunction>

	<cffunction name="getAt" access="public" returntype="com.revenea.root.Object" output="false" hint="Returns the object stored at the specified index from the contained set.">
		<cfargument name="nIndex" type="numeric" required="true" />

		<cfset var oObj = "" />

		<!--- Range validation to ensure nIndex is within the recordSet and objectSet boundaries --->
		<cfset validateSetIndex(arguments.nIndex) />

		<cftry>
			<!--- Return the populated/loaded object stored at the requested index --->
			<cfreturn variables.instance.objectSet[arguments.nIndex] />
			<cfcatch type="expression">

				<!--- ::: An expression exception means it wasn't yet defined ::: --->

				<!--- Set the current row of the private recordSet to the specified index, which will carry through to the populate method --->
				<cfloop query="variables.instance.recordSet">
					<cfif (variables.instance.recordSet.CurrentRow EQ arguments.nIndex)>
						<!--- The object hasn't been instantiated and loaded yet, we need to do this now --->
						<cfset oObj = CreateObject("component", getCargoTypePath()).init(0, variables.instance.recordSet) />
						<cfbreak />
					</cfif>
				</cfloop>
				<!--- Store it in the collection for retrieval --->
				<cfset variables.instance.objectSet[arguments.nIndex] = oObj />
				<!--- Now that it's been defined, return the object --->
				<cfreturn variables.instance.objectSet[arguments.nIndex] />
			</cfcatch>
		</cftry>
	</cffunction>

	<cffunction name="clearRecordSet" returnType="boolean" access="public" output="false" hint="Sets the objectSet property to a zero count array.">
		<cfscript>
			// Clear the set
			variables.instance.recordSet = QueryNew("");
			// Also, clear the self history data
			clearSelfHistory();
			// Return success boolean
			return true;
		</cfscript>
	</cffunction>

	<cffunction name="clearObjectSet" returnType="boolean" access="public" output="false" hint="Sets the objectSet property to a zero count array.">
		<cfscript>
			// Clear the set
			variables.instance.objectSet = ArrayNew(1);
			// Also, clear the self history data
			clearSelfHistory();
			// Return success boolean
			return true;
		</cfscript>
	</cffunction>

	<cffunction name="toWddx" returnType="string" access="public" output="false" hint="Returns XML string of WDDX serialized array of objects via the object set property.&nbsp; These wddx methods are useful when handling collections via AJAX.">
		<cfscript>
			// Init vars
			var local = StructNew();
			local.sWddx = "";
			// Make WDDX goodness!
			local.stParams = StructNew();
			local.stParams.action = "cfml2wddx";
			local.stParams.input = variables.instance.objectSet;
			local.stParams.output = "local.sWddx";
			cfwddx(local.stParams);
			return local.sWddx;
		</cfscript>
	</cffunction>

	<!--- TODO: Add a toJSON method --->

	<cffunction name="getByIdList" returnType="void" access="public" output="false" hint="Loads up the collection by matching all the ID values in the list provided.">
		<cfargument name="sIdList" type="string" required="true" hint="This is required to be a comma-delimited list string." />
		<!--- TODO: Implement this method --->
	</cffunction>

	<cffunction name="dump" access="public" returntype="void" output="true" hint="Returns a structure containing all private non-function variables for this object.">
		<cfargument name="blnAbort" type="boolean" required="false" default="false" />
		<cfdump var="#variables.instance#" />
		<cfif (arguments.blnAbort)><cfabort /></cfif>
	</cffunction>


	<!--- Private methods --->


	<cffunction name="setSelfHistory" returnType="void" access="private" output="false" hint="Copies the keyValue, keyTypeID, and name of last getter method executed into the history properties lastLoadedKeyValue, lastLoadedKeyTypeID, and lastLoadedMethod">
		<cfargument name="sMethodName" type="string" required="true" hint="Indicates the method that just executed" />
		<cfargument name="stKeySet" type="struct" required="true" hint="Optional key set that is a struct.&nbsp; This replaces the need for the second and third arguments, and the method will internally react to this configuration of arguments in an appropriate manner." />
		<cfscript>
			// Store the key data and executed method name into collection properties for reference
			setLastLoadedMethod(arguments.sMethodName);
			setLastLoadedDate(Now());
			// Validate the key set before assigning it
			if ((IsStruct(arguments.stKeySet)) AND (NOT StructIsEmpty(arguments.stKeySet)))
			{
				setLastLoadedKeySet(arguments.stKeySet);
			}
		</cfscript>
	</cffunction>

	<cffunction name="clearSelfHistory" returnType="void" access="private" output="false" hint="Clears all property values that store information about where the data contained in the collection came from.">
		<cfscript>
			setLastLoadedMethod("");
			setLastLoadedKeySet(StructNew());
			setLastLoadedDate(Now());
		</cfscript>
	</cffunction>

	<cffunction name="clearSets" returnType="boolean" access="private" output="false" hint="Clears both sets, as defined in their respective methods">
		<cfscript>
			// Clear the sets
			clearRecordSet();
			clearObjectSet();
			// Also, clear the self history data
			clearSelfHistory();
			// Return success boolean
			return true;
		</cfscript>
	</cffunction>

	<cffunction name="deriveCargoTypePath" access="private" displayname="Derive Cargo Type Path" output="false" returntype="string" hint="Using string parsing, attempts to make a best-guess at the ColdFusion component path of the object types for which this collection was defined.  Aides in the instantiating that is performed inside getAt() method.">
		<cfscript>
			var local = StructNew();
			// Retrieve the object meta data
			local.oMetaData = getMetaData(this);
			// Init the return string
			local.sPathList = "";
			local.sNewName = "";
			local.nListLen = 0;
			local.nSecondToLast = 0;

			// Ensure that .name from the getMetaData function return struct is a non-empty string
			if (Len(local.oMetaData.name) EQ 0)
			{
				local.stThrowParams = StructNew();
				local.stThrowParams.type = "API.Validation";
				local.stThrowParams.message = "Attempt to derive cargo type path failed.";
				local.stThrowParams.detail = "The metadata for the current object (name=" & local.oMetaData.name & ") does not contain enough information to derive the cargo type path.";
				cfthrow(local.stThrowParams);
			}

			// Store the list length (used many times here)
			local.nListLen = ListLen(local.oMetaData.name, ".");
			// First try to replace the second to last from "collection" to "data", which will only work for newer (fall 2008+) collections
			if (local.nListLen GT 1)
			{
				local.nSecondToLast = (local.nListLen - 1);
				if (ListGetAt(local.oMetaData.name, local.nSecondToLast, ".") EQ "collection")
				{
					// It's a new style collection path, so simply replace "collection" with "data"
					local.sPathList = ListSetAt(local.oMetaData.name, local.nSecondToLast, "data", ".");
				}
				else
				{
					// It's an old style, so we need to replace "data" with "access" and trim "collection" off of the last list item
					local.sNewName = ReplaceNoCase(ListLast(local.oMetaData.name, "."), "collection", "", "all");
					local.sPathList = ListSetAt(local.oMetaData.name, local.nListLen, local.sNewName, ".");
				}
			}

			// Return the final path list
			return local.sPathList;
		</cfscript>
	</cffunction>

	<cffunction name="deriveAccessComponentPath" access="private" displayname="Derive Access Component" output="false" returntype="string" hint="Using list a replacement technique on getMetaData() results, this method will return the full path to the access component for this collection.">
		<cfscript>
			var local = StructNew();
			// Retrieve the object meta data
			local.oMetaData = getMetaData(this);
			// Init the return string
			local.sAccessPathList = "";
			local.sNewName = "";
			local.nListLen = 0;
			local.nSecondToLast = 0;

			// Ensure that .name from teh getMetaData result struct is a non-empty string
			if (Len(local.oMetaData.name) EQ 0)
			{
				local.stThrowParams = StructNew();
				local.stThrowParams.type = "API.Validation";
				local.stThrowParams.message = "Attempt to derive Access component failed.";
				local.stThrowParams.detail = "The metadata for the current object (name=" & local.oMetaData.name & ") does not contain enough information to derive the Access component path.";
				cfthrow(local.stThrowParams);
			}

			// Store the list length (used many times here)
			local.nListLen = ListLen(local.oMetaData.name, ".");
			// First try to replace the second to last from "collection" to "access", which will only work for newer (fall 2008+) collections
			if (local.nListLen GT 1)
			{
				local.nSecondToLast = (local.nListLen - 1);
				if (ListGetAt(local.oMetaData.name, local.nSecondToLast, ".") EQ "collection")
				{
					// It's a new style collection path, so simply replace "collection" with "access"
					local.sAccessPathList = ListSetAt(local.oMetaData.name, local.nSecondToLast, "Access", ".");
				}
				else
				{
					// It's an old style, so we need to replace "data" with "access" and trim "collection" off of the last list item
					local.sNewName = ReplaceNoCase(ListLast(local.oMetaData.name, "."), "collection", "", "all");
					local.sAccessPathList = ListSetAt(local.oMetaData.name, local.nListLen, local.sNewName, ".");
					local.sAccessPathList = ListSetAt(local.sAccessPathList, local.nSecondToLast, "Access", ".");
				}
			}

			// Return the final path list
			return local.sAccessPathList;
		</cfscript>
	</cffunction>

	<cffunction name="validateSetIndex" access="private" returntype="void" hint="Range validation to ensure nIndex is within the recordSet and objectSet boundaries">
		<cfargument name="nIndex" type="numeric" required="true" />
		<cfscript>
			var local = StructNew();
			local.blnIsValid = true;
			if (arguments.nIndex LT 1)
			{
				local.blnIsValid = false;
			}
			if ((arguments.nIndex GT variables.instance.recordSet.recordCount) OR (arguments.nIndex GT ArrayLen(variables.instance.objectSet)))
			{
				local.blnIsValid = false;
			}
		</cfscript>
		<cfif (NOT local.blnIsValid)>
			<cfthrow type="com.revenea.api.validation" message="Validation error while executing getAt method.  The requested index #arguments.nIndex# is out of range." detail="The collection currently contains a record set with #variables.instance.recordSet.recordCount# records and an array with #ArrayLen(variables.instance.objectSet)# elements." />
		</cfif>
	</cffunction>

	<cffunction name="setDefaults" returnType="void" access="private" output="false" hint="Sets all properties with accepted default values">
		<cfscript>
			// Set private properties
			variables.instance.id = CreateUUID();
			variables.instance.recordSet = QueryNew("");
			variables.instance.objectSet = ArrayNew(1);
			setCargoTypePath(deriveCargoTypePath());
			setLastLoadedDate(Now());
			setLastLoadedMethod("");
			setLastLoadedKeySet(StructNew());
			setDAOPath(deriveAccessComponentPath());
		</cfscript>
	</cffunction>


</cfcomponent>